package com.simpleplayer.contentview;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.view.Surface;
import android.view.View;

import androidx.annotation.NonNull;

import com.simpleplayer.contentview.gles.DirectRenderer;
import com.simpleplayer.contentview.gles.GLESRenderer;
import com.simpleplayer.contentview.gles.GLHelper;
import com.simpleplayer.contentview.gles.SphereRenderer;
import com.simpleplayer.services.LogUtil;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

@SuppressLint("ViewConstructor")
class ContentGLSurfaceView extends GLSurfaceView implements ContentView {

    private final static String TAG = "ContentGLSurfaceView";

    private final SurfaceListener mSurfaceListener;
    private final Handler mMainHadler;

    private int mContentWidth = 0;
    private int mContentHeight = 0;

    public ContentGLSurfaceView(Context context, SurfaceListener listener, @RENDERTYPE int type) {
        super(context);
        this.mSurfaceListener = listener;
        this.mMainHadler = new Handler();

        //GLContext设置OpenGLES2.0
        setEGLContextClientVersion(2);
        setPreserveEGLContextOnPause(false);
        setRenderer(new VideoRenderer(type));
        // 渲染方式，RENDERMODE_WHEN_DIRTY表示被动渲染，只有在调用requestRender或者onResume等方法时才会进行渲染。
        // RENDERMODE_CONTINUOUSLY表示持续渲染
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = 1;
        int height = 1;
        if (mContentWidth > 0 && mContentHeight > 0) {
            width = getDefaultSize(mContentWidth, widthMeasureSpec);
            height = getDefaultSize(mContentHeight, heightMeasureSpec);
            if (mContentWidth * height  > width * mContentHeight) {
                height = width * mContentHeight / mContentWidth;
            } else if (mContentWidth * height  < width * mContentHeight) {
                width = height * mContentWidth / mContentHeight;
            }
        }
        setMeasuredDimension(width, height);
    }

    @Override
    public void onContentSizeChanged(int width, int height) {
        // ignore size 0, it's meaningless
        if (width == 0 || height == 0)
            return;
        if (mContentWidth != width || mContentHeight != height) {
            mContentWidth = width;
            mContentHeight = height;
            getHolder().setFixedSize(mContentWidth, mContentHeight);
            requestLayout();
        }
    }

    @NonNull
    @Override
    public GLSurfaceView getView() {
        return this;
    }

    private class VideoRenderer
            implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {

        private final float[] mSurfaceTextureMatrix;
        private final GLESRenderer mRenderer;

        private SurfaceTexture mSurfaceTexture;
        private SurfaceTexture mOldSurfaceTexture;
        private Surface mSurface;
        private Surface mOldSurface;

        private boolean mUpdateSurface = false;
        private int mTextureID = -1;
        private int mWidth;
        private int mHeight;

        public VideoRenderer(@RENDERTYPE int type) {
            if (type == DIRECT) {
                mRenderer = new DirectRenderer();
            } else {
                mRenderer = new SphereRenderer();
            }

            mSurfaceTextureMatrix = new float[16];
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            // release internal surface when released.
            if (mSurface != null) {
                mSurface.release();
            }
        }

        @Override
        public void onFrameAvailable(SurfaceTexture surfaceTexture) {
            setNeedsRedraw(true);
            ContentGLSurfaceView.this.requestRender();
        }

        private synchronized void setNeedsRedraw(boolean need) {
            mUpdateSurface = need;
        }

        private SurfaceTexture createInternalSurfaceTexture() {
            SurfaceTexture texture = new SurfaceTexture(mTextureID);
            texture.setOnFrameAvailableListener(VideoRenderer.this);
            return texture;
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            // create Texture and load gl shader program each on the new gl context
            mTextureID = GLHelper.generateExternalTexture();
            mRenderer.initGLProgram();

            // use internal surface texture.
            synchronized (this) {
                SurfaceTexture texture = createInternalSurfaceTexture();
                Surface surface = new Surface(texture);

                if (mSurfaceListener != null) {
                    mSurfaceListener.onSurfaceDestroy(mSurface);
                    mSurfaceListener.onSurfaceCreate(surface);
                }
                setNeedsRedraw(false);
                mOldSurface = mSurface;
                mOldSurfaceTexture = mSurfaceTexture;
                mSurface = surface;
                mSurfaceTexture = texture;

                // 延迟200ms再释放旧的surface, 否则 ExoPlayer 可能由于未及时更新 surface 而报错
                mMainHadler.postDelayed(this::releaseOldSurface, 200);
            }

            LogUtil.i(TAG, "onSurfaceCreated thread id = " +
                    Thread.currentThread().getId() + " texture id = " + mTextureID);
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            LogUtil.i(TAG, "onSurfaceChanged width = " + width + " height = " + height);
            GLES20.glViewport(0, 0, width, height);

            mRenderer.updateViewport(width, height);
            mWidth = width;
            mHeight = height;
        }

        @Override
        public void onDrawFrame(GL10 gl) {
            onDrawFrameInternal(gl);
        }

        private void onDrawFrameInternal(GL10 gl) {
            try {
                synchronized (this) {
                    if (mUpdateSurface && mSurfaceTexture != null) {
                        mSurfaceTexture.updateTexImage();
                        mSurfaceTexture.getTransformMatrix(mSurfaceTextureMatrix);
                        setNeedsRedraw(false);
                    }
                }
                if (!mRenderer.isInited()) {
                    mRenderer.initGLProgram();
                    mRenderer.updateViewport(mWidth, mHeight);
                }
                mRenderer.drawFrame(mTextureID, mSurfaceTextureMatrix);
            } catch (Exception e) {
                LogUtil.e(TAG, " onDrawFrameInternal ", e);
            }
        }

        private void releaseOldSurface() {
            if (mOldSurfaceTexture != null) {
                mOldSurfaceTexture.release();
            }
            if (mOldSurface != null) {
                mOldSurface.release();
            }
        }
    }
}
